#ifndef DObjMgr_H
#define DObjMgr_H

#include <QDebug>
#include <QObject>
#include <iostream>
#include <memory>
#include <sstream>
#include <type_traits>
#include <unordered_map>
#include <vector>

#include "DLogInfoFormat.h"

#define ObjectDefaultID 0

// ObjPtr
template <class T>
inline std::string dGetClassName()
{
    return std::string("unKown");
}

#define D_DECLARE_OBJ(IFace, ObjID)           \
    template <>                               \
    inline std::string dGetClassName<IFace>() \
    {                                         \
        return std::string(ObjID);            \
    }

#define D_REGISTER_OBJECT1(ClassName) g_objMgr.addObj(new ClassName())
#define D_REGISTER_OBJECT2(ClassName, ClassParent) \
    g_objMgr.addObj<ClassName, ClassParent>(new ClassName())

#define D_REGISTER_OBJECT3(ClassName, objPtr) g_objMgr.addObj(objPtr)

class WrapperPointer
{
public:
    explicit WrapperPointer() = default;
    virtual ~WrapperPointer() = default;
};

template <typename _interface>
struct DPointer : public WrapperPointer
{
    ~DPointer()
    {
        if (ptr != nullptr)
        {
            delete ptr;
            ptr = nullptr;
        }
    }
    _interface *ptr = nullptr;
};

using DObjKey = std::tuple<std::string, int64_t>;

struct es_hash_fn
{
    std::size_t operator()(const DObjKey &id) const
    {
        std::string strName = std::get<0>(id);
        strName += std::to_string(std::get<1>(id));
        return std::hash<std::string>()(strName);
    }
};

template <typename _interface>
struct ObjectID
{
    explicit ObjectID(const std::string &strClassName, const int64_t &nIDIndex)
        : objKey(std::make_tuple(strClassName, nIDIndex))
    {
        // qDebug() << strClassName.c_str() << nIDIndex;
    }

    std::string toString() const
    {
        std::stringstream ss;
        ss << std::get<0>(objKey) << "." << std::get<1>(objKey);
        return ss.str();
    }

    DObjKey objKey;
};

class DObjMgr final
{
public:
    explicit DObjMgr() = default;
    virtual ~DObjMgr() = default;

    void releaseObject()
    {
        std::for_each(m_mapObj.begin(), m_mapObj.end(),
                      [](const std::pair<DObjKey, WrapperPointer *> &p)
                      { delete p.second; });
        m_mapObj.clear();
    }

    int count() const { return m_mapObj.size(); }

    template <typename _construct, typename _interface = _construct>
    ObjectID<_interface> addObj(_construct *obj,
                                const int64_t &nIndex = ObjectDefaultID)
    {
        ObjectID<_interface> objID(dGetClassName<_interface>(), nIndex);

        if (m_mapObj.find(objID.objKey) == m_mapObj.end())
        {
            DPointer<_interface> *pes = new DPointer<_interface>;
            pes->ptr = dynamic_cast<_interface *>(obj);
            m_mapObj[objID.objKey] = pes;
        }
        else
        {
            qErrorCode(QString::fromStdString(objID.toString()));
        }

        return objID;
    }

    template <typename _interface>
    _interface *getObj(const ObjectID<_interface> &objID)
    {
        // LOG_INFO(objID.toString());

        if (m_mapObj.find(objID.objKey) == m_mapObj.end())
        {
            return nullptr;
        }

        auto esPtr =
            dynamic_cast<DPointer<_interface> *>(m_mapObj[objID.objKey]);
        if (esPtr == nullptr)
        {
            return nullptr;
        }
        return esPtr->ptr;
    }

private:
    std::unordered_map<DObjKey, WrapperPointer *, es_hash_fn> m_mapObj;
};

extern DObjMgr g_objMgr;

#endif  // DObjMgr_H
